{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- https://man7.org/linux/man-pages/man7/epoll.7.html\n",
    "- /usr/include/x86_64-linux-gnu/sys/epoll.h\n",
    "- https://sites.uclouvain.be/SystInfo/usr/include/sys/epoll.h.html"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "1. **LT**：默认模式，不加 `EPOLLET`；语义等同于 `poll`，但效率更高。\n",
    "2. **ET**：需加 `EPOLLET`，内核只在状态边缘变化时通知；必须配合非阻塞 fd 并读/写到 `EAGAIN`。\n",
    "3. `epoll` 的高效来源于：Interest List + Ready List 的分离、驱动层回调、就绪队列常数级处理，以及与各种内核 fd 对象的无缝整合。\n",
    "4. 内核提供 `EPOLLONESHOT`、`EPOLLWAKEUP` 等扩展 flag，帮助开发者在多线程和省电场景中稳定运行。\n",
    "5. 手册提供的示例与 Q&A 涵盖了 ET 使用的常见坑点，是高质量工程实现的重要参考。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 触发模式速览：LT vs ET"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| 触发模式                          | 触发条件                                                     | 读取/写入要求                                                | 典型场景                               | 注意事项                                         |\n",
    "| --------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | -------------------------------------- | ------------------------------------------------ |\n",
    "| **LT（Level Trigger，水平触发）** | 只要内核缓冲区仍有数据（或可写空间），每次 `epoll_wait` 都会返回该事件 | 事件到来时读/写一次即可；不要求读空                          | 简单 Reactor，连接数较少或重视编程简洁 | 重复通知较多；相当于“更快的 `poll`”              |\n",
    "| **ET（Edge Trigger，边缘触发）**  | 仅在状态发生“边缘变化”时触发（如无数据→有数据）              | 必须设为非阻塞，且循环 `read/write` 直到返回 `EAGAIN/EWOULDBLOCK` | 高并发服务、追求更低事件开销的场景     | 若未一次读空缓冲区会漏事件；逻辑复杂，调试难度高 |\n",
    "\n",
    "配置方式  \n",
    "- LT: `ev.events = EPOLLIN;`（默认不加 `EPOLLET`）,  `epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);`\n",
    "- ET: `ev.events = EPOLLIN | EPOLLET;` , `epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);` "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# `epoll` 在内核里的支撑机制（结合 man-pages）"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**内核数据结构**\n",
    "\n",
    "`epoll(7)` 手册指出：一个 epoll 实例（由 `epoll_create` / `epoll_create1` 创建）在内核中主要维护两份列表：\n",
    "\n",
    "- **Interest List（兴趣列表 / epoll set）**：保存所有已注册的文件描述符及其事件掩码。\n",
    "- **Ready List（就绪列表）**：Interest List 的子集，记录当前真正“就绪”的 fd 引用，由内核在检测到 IO 活动时动态填充。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**核心系统调用流程**\n",
    "\n",
    "| 调用                                   | 内核工作                                           | 说明                                                   |\n",
    "| -------------------------------------- | -------------------------------------------------- | ------------------------------------------------------ |\n",
    "| `epoll_create(2)` / `epoll_create1(2)` | 初始化 `struct eventpoll`，返回 epoll 控制器的 fd  | `epoll_create1` 支持 `EPOLL_CLOEXEC` 等 flag           |\n",
    "| `epoll_ctl(2)`                         | 向 Interest List 添加/修改/删除 fd，并注册驱动回调 | 回调由驱动的 `poll` 方法提供，用来在状态变化时投递事件 |\n",
    "| `epoll_wait(2)`                        | 读取 Ready List，必要时阻塞线程，直到有事件        | 可视作“把 Ready List 中的事件批量拷贝给用户态”         |\n",
    "\n",
    "Ready List 的填充由底层驱动在状态变化时自动完成：当某个 fd 满足条件（例如可读/可写）时，对应的回调函数会把该 fd 的 `epitem` 加入 Ready List，并唤醒阻塞在 `epoll_wait` 上的线程。\n",
    "\n",
    "\n",
    "```txt\n",
    "        ┌────────────┐     状态变化      ┌──────────┐\n",
    "        │ Interest   │ ───────────────▶ │ Ready    │\n",
    "        │ List       │   内核回调填充     │ List     │\n",
    "        └────────────┘                   └──────────┘\n",
    "               ▲                              │\n",
    "       epoll_ctl 增删改                  epoll_wait 读取\n",
    "               │                              ▼\n",
    "        （用户态配置）        应用消费事件 / 内核决定是否保留\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# LT 与 ET 的内核语义与操作建议"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| 维度            | LT（默认模式）                                               | ET（需 `EPOLLET`）                                           |\n",
    "| --------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |\n",
    "| Ready List 行为 | 内核会保留就绪 fd，直到应用真正消耗完；`epoll_wait` 可反复看到同一 fd | 只在状态变化瞬间加入 Ready List，一旦被取出就视为“已消费”    |\n",
    "| 使用场景        | 当作更高效的 `poll`，语义相同，便于迁移                      | 高并发，减少重复通知和惊群，适合精细的事件循环               |\n",
    "| 编程要求        | 阻塞 / 非阻塞均可；读写逻辑简单                              | **必须非阻塞**；回调里需循环读取/写入至返回 `EAGAIN`         |\n",
    "| 手册提示        | “epoll 在 LT 下就是更快的 `poll`”                            | 官方示例强调：读 2 KiB，读走 1 KiB 后若未读空，下一次 `epoll_wait` 可能阻塞 |\n",
    "\n",
    "> `epoll(7)` 明确建议：使用 ET 时需配合非阻塞 fd，以及“仅在 `read/write` 返回 `EAGAIN` 后再等待下一次事件”。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "“**惊群效应**”（Thundering Herd）指的是：当多个线程或进程在同一个资源（例如同一个监听 socket）的事件上等待时，一旦资源就绪，**所有等待者都会被同时唤醒**，但最终只有其中一个能真正获取这次事件，其余线程/进程会发现“白跑一趟”，又重新回去等待。这个过程会反复发生，带来大量无谓的上下文切换和 CPU 争抢。\n",
    "\n",
    "在监听 socket 场景下：\n",
    "\n",
    "1. 多个 accept 线程/进程同时阻塞在同一个监听 socket 上，等待新连接。\n",
    "2. 有新连接到来时，内核会把所有等待者都唤醒：\n",
    "   - 大家抢着调用 `accept()`。\n",
    "   - 只有一个成功，其余线程发现没有可接受的连接，只能再次睡眠。\n",
    "3. 结果就是“群体惊醒又马上没活干”，造成性能浪费。\n",
    "\n",
    "`EPOLLEXCLUSIVE` 标志可以让 `epoll_wait` 在监听 socket 上只唤醒 **一个**等待的线程/进程，从而缓解这种“惊群”现象，提高效率。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 手册中的 ET 案例与建议"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 情景：管道写入 2 KiB → `epoll_wait` 返回可读 → 读走 1 KiB → 再次 `epoll_wait`。\n",
    "  - 若使用 `EPOLLET` 且未读空会导致挂起，远端可能已等待应答。\n",
    "- 官方建议的 ET 使用方式：\n",
    "  1. 将 fd 设置为非阻塞。\n",
    "  2. 只在 `read/write` 返回 `EAGAIN` 后再次等待事件。\n",
    "- 手册示例中提供了典型流程（监听 socket → `accept` 新连接 → 设置新 fd 为非阻塞，注册 `EPOLLIN | EPOLLET` → 在 `do_use_fd()` 中循环处理直到 `EAGAIN`）。\n",
    "- 为减少重新 `epoll_ctl` 的次数，可一次性以 `EPOLLIN | EPOLLOUT | EPOLLET` 注册，让内核在读/写方向都触发边缘事件。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 关键标志与扩展功能"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| 标志                                  | 作用                                                         | 适用场景                                |\n",
    "| ------------------------------------- | ------------------------------------------------------------ | --------------------------------------- |\n",
    "| `EPOLLONESHOT`                        | 事件触发一次后自动禁用，需要 `EPOLL_CTL_MOD` 重新激活        | 多线程处理一个 fd，防止多个线程同时读取 |\n",
    "| `EPOLLWAKEUP`                         | 与 autosleep 配合：事件到达时保持系统唤醒，直到下一次 `epoll_wait` | 移动/嵌入式设备上避免休眠导致丢事件     |\n",
    "| `EPOLLEXCLUSIVE`（见 `epoll_ctl(2)`） | 降低惊群效应（在监听 socket 上常见）                         | 多进程/线程等待同一监听 fd              |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 内核限制与 `/proc` 支撑"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- `/proc/sys/fs/epoll/max_user_watches`：限制单个用户注册的 fd 数，默认约为可用低端内存的 4% / 每个 fd 的开销。\n",
    "- `/proc/<pid>/fdinfo/<epfd>`：可查看某 epoll 实例当前的 Interest List 状态，便于调试。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 常见问题（Q&A 精选）"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| 问题                                         | 答复（节选自 `epoll(7)`）                                    |\n",
    "| -------------------------------------------- | ------------------------------------------------------------ |\n",
    "| 同一 fd 能重复添加吗？                       | 直接添加会 `EEXIST`；但可用 `dup`/`dup2` 创建“不同的 open file description”并注册不同事件掩码，实现事件过滤。 |\n",
    "| epoll 自身可被 epoll/poll/select 监控吗？    | 可以，如果 Ready List 非空，该 epoll fd 会变得可读。         |\n",
    "| 关闭 fd 会自动从 epoll 中删除吗？            | 只有当指向同一 open file description 的所有 fd 都关闭后才移除；若曾 `dup`，需 `EPOLL_CTL_DEL` 清理。 |\n",
    "| 多个事件在两次 `epoll_wait` 之间发生会如何？ | 会合并（事件掩码取并集）。                                   |\n",
    "| 使用 ET 时是否一定要读到 `EAGAIN`？          | 是，一旦 `read` 返回短读或 `EAGAIN` 才可认为数据耗尽，否则可能错过后续事件。 |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 与其他内核机制的配合"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- `eventfd`、`timerfd`、`signalfd` 等内核对象都可以与 epoll 联动，实现统一的事件循环。\n",
    "- `epoll` 支持嵌套——可以将一个 epoll fd 注册到另一个 epoll 中（但不可自注册）。\n",
    "- 与线程池配合：可搭配 `EPOLLONESHOT`，由工作线程处理完事件后再通过 `EPOLL_CTL_MOD` 重新启用，避免惊群。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 可能的陷阱与规避（man-pages 提醒）"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| 陷阱                     | 原因                                                   | 规避策略                                                |\n",
    "| ------------------------ | ------------------------------------------------------ | ------------------------------------------------------- |\n",
    "| **ET 下的饥饿**          | 某个 fd 数据太多，单次处理耗时过长，其他 fd 得不到机会 | 在应用层维护 ready 队列 + 轮询处理                      |\n",
    "| **事件缓存混乱**         | 在处理批量事件时中途关闭了某个 fd，却仍在缓存中处理它  | 关闭时同步 `EPOLL_CTL_DEL` 并标记数据结构，避免重复处理 |\n",
    "| **autosleep 中提前休眠** | 未使用 `EPOLLWAKEUP`，事件排入队列后系统立即回睡       | 使用 `EPOLLWAKEUP` 或额外的 wake_lock                   |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# epoll为什么比select, poll等性能高"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "I/O 多路复用核心对比\n",
    "\n",
    "| 特性维度              | `select`                                     | `poll`                                   | `epoll`                                                      |\n",
    "| --------------------- | -------------------------------------------- | ---------------------------------------- | ------------------------------------------------------------ |\n",
    "| **fd 数量限制**       | 受 `FD_SETSIZE`（默认 1024）限制             | 使用链表存储，无固定上限                 | 理论无限制，内核红黑树结构管理                               |\n",
    "| **就绪检测复杂度**    | 每次线性扫描全部 fd，复杂度 **O(N)**         | 同样线性扫描，复杂度 **O(N)**            | 内核维护就绪队列，`epoll_wait` 近似 **O(就绪数)**            |\n",
    "| **用户态/内核态拷贝** | 每次调用需重新拷贝 fd 集合                   | 同样需要每次拷贝                         | 注册一次，多次使用；等待阶段无需重复拷贝                     |\n",
    "| **触发模式**          | 仅支持水平触发（LT）                         | 仅支持水平触发（LT）                     | 支持水平触发（LT）和边缘触发（ET），并提供 `EPOLLEXCLUSIVE` 降低惊群 |\n",
    "| **高并发适配性**      | 100 万连接需上千进程，轮询与上下文切换成本高 | 虽无数量限制，但仍需线性遍历，扩展性不足 | 单个 `epoll` 对象即可管理百万连接，尤其适合“海量连接 + 少量活跃”场景 |\n",
    "| **典型使用**          | 早期兼容性、简单脚本                         | 相对较新的替代方案                       | Linux 下主流高性能网络库（Muduo、Java NIO 的 Selector 等）   |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "LT（水平触发）与 ET（边缘触发）比较\n",
    "\n",
    "| 触发模式 | 行为特征                                        | 使用建议与风险                                               |\n",
    "| -------- | ----------------------------------------------- | ------------------------------------------------------------ |\n",
    "| **LT**   | 只要未处理完数据就持续触发；易用、不丢事件      | 与 `select`/`poll` 行为一致；延迟低；更公平；适合绝大多数场景 |\n",
    "| **ET**   | 状态变化瞬间触发一次；必须一次性读写至 `EAGAIN` | 需配合非阻塞 IO；编程复杂；可能导致饥饿或漏事件              |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- `select` 与 `poll` 的主要瓶颈源于“全量扫描 + 频繁拷贝”；随着连接数增大，CPU 与内核态切换开销迅速膨胀。\n",
    "- `epoll` 通过在内核内构建红黑树和就绪链表，实现了监控集合的常驻和事件的就绪队列化；百万级连接只需注册一次，后续 `epoll_wait` 直接返回就绪 fd。\n",
    "- Muduo 等高性能网络库选择 **LT 模式**，原因包括：行为可靠、延迟低、避免单连接“独占”事件、跨平台兼容性好，以及无需强制循环读写到 `EAGAIN`。\n",
    "- 实际压测表明：触发模式并非决定性能的唯一因素，线程模型、业务延迟与调度策略同样关键。Muduo 在多线程场景下可达到甚至超过使用 ET 的 libevent。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// select：实现最简单、兼容最好，但每轮都要重建集合并线性扫描，容易被 fd 数量拖垮。\n",
    "\n",
    "fd_set read_set;\n",
    "int max_fd = listen_fd;\n",
    "\n",
    "while (1) {\n",
    "    FD_ZERO(&read_set);                     // ❌ 每次都要重新构建 fd 集合，造成重复工作\n",
    "    FD_SET(listen_fd, &read_set);\n",
    "\n",
    "    for (int fd : active_fds) {\n",
    "        FD_SET(fd, &read_set);              // ❌ O(N) 将全部 fd 拷入 bitmap，fd 数越多越慢\n",
    "        if (fd > max_fd) max_fd = fd;       // ❌ 需要维护最大 fd，若 fd 分散则效率低\n",
    "    }\n",
    "\n",
    "    int ready = select(max_fd + 1, &read_set, NULL, NULL, NULL);\n",
    "    if (ready <= 0) continue;\n",
    "\n",
    "    if (FD_ISSET(listen_fd, &read_set)) {\n",
    "        int conn_fd = accept(listen_fd, NULL, NULL);\n",
    "        active_fds.insert(conn_fd);         // ✅ 使用简单，几乎所有平台可用\n",
    "    }\n",
    "\n",
    "    for (int fd : active_fds) {\n",
    "        if (FD_ISSET(fd, &read_set)) {      // ❌ 必须遍历全部 fd 才能找到就绪者\n",
    "            ssize_t n = recv(fd, buf, sizeof(buf), 0);\n",
    "            if (n <= 0) {                   // ✅ 行为直观，未处理完会继续触发\n",
    "                close(fd);\n",
    "                active_fds.erase(fd);\n",
    "            } else {\n",
    "                send(fd, buf, n, 0);\n",
    "            }\n",
    "        }\n",
    "    }\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// poll：突破 FD_SETSIZE 限制，但仍需遍历整个数组，数组删除/移动也会引入额外开销\n",
    "\n",
    "std::vector<pollfd> pollfds;\n",
    "pollfds.push_back({listen_fd, POLLIN, 0});\n",
    "\n",
    "while (1) {\n",
    "    int ready = poll(pollfds.data(), pollfds.size(), -1);\n",
    "    if (ready <= 0) continue;\n",
    "\n",
    "    if (pollfds[0].revents & POLLIN) {\n",
    "        int conn_fd = accept(listen_fd, NULL, NULL);\n",
    "        pollfds.push_back({conn_fd, POLLIN, 0});   // ✅ 不受 1024 限制，可监视海量 fd\n",
    "    }\n",
    "\n",
    "    for (size_t i = 1; i < pollfds.size(); ++i) {\n",
    "        if (pollfds[i].revents & POLLIN) {         // ❌ 依旧是 O(N) 线性扫描，fd 多时耗时\n",
    "            ssize_t n = recv(pollfds[i].fd, buf, sizeof(buf), 0);\n",
    "            if (n <= 0) {\n",
    "                close(pollfds[i].fd);\n",
    "                pollfds.erase(pollfds.begin() + i); // ❌ 删除需要移动数组，频繁操作成本高\n",
    "                --i;\n",
    "            } else {\n",
    "                send(pollfds[i].fd, buf, n, 0);\n",
    "            }\n",
    "        }\n",
    "        pollfds[i].revents = 0;                   // ❌ revents 每轮都要清零，仍有重复工作\n",
    "    }\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// epoll：事件注册与等待分离，内核维护就绪队列，epoll_wait 只返回活跃 fd，是 Linux 下高并发场景的主流选择。\n",
    "\n",
    "int epfd = epoll_create1(0);                       // ✅ 内核创建 eventpoll，只做一次\n",
    "\n",
    "epoll_event ev, events[MAX_EVENTS];\n",
    "ev.events = EPOLLIN;\n",
    "ev.data.fd = listen_fd;\n",
    "epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &ev);    // ✅ 注册一次，后续无需重复提交整个集合\n",
    "\n",
    "while (1) {\n",
    "    int nready = epoll_wait(epfd, events, MAX_EVENTS, -1);\n",
    "    for (int i = 0; i < nready; ++i) {\n",
    "        int fd = events[i].data.fd;\n",
    "\n",
    "        if (fd == listen_fd) {\n",
    "            int conn_fd = accept(listen_fd, NULL, NULL);\n",
    "            epoll_event cev;\n",
    "            cev.events = EPOLLIN;                 // ✅ LT 模式：未读完内核会继续通知，避免丢数据\n",
    "            // 若改用 ET，需要配合非阻塞并循环读到 EAGAIN，否则可能漏事件\n",
    "            cev.data.fd = conn_fd;\n",
    "            epoll_ctl(epfd, EPOLL_CTL_ADD, conn_fd, &cev);\n",
    "        } else if (events[i].events & EPOLLIN) {\n",
    "            ssize_t n = recv(fd, buf, sizeof(buf), 0);\n",
    "            if (n <= 0) {\n",
    "                epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);  // ✅ 删除成本 O(log N)，高并发依旧可控\n",
    "                close(fd);\n",
    "            } else {\n",
    "                send(fd, buf, n, 0);             // ✅ 只处理就绪 fd，避免无效遍历\n",
    "            }\n",
    "        }\n",
    "    }\n",
    "}"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "C++17",
   "language": "C++17",
   "name": "xcpp17"
  },
  "language_info": {
   "name": "C++17"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
